package client_core

import (
	"context"
	"errors"
	"github.com/chicken-team-outside/chicken_transmission/client_core/service"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"go.uber.org/zap"
	"sync"
	"sync/atomic"
)

type TaskWarp struct {
	info      *model.Task
	completed atomic.Int64
	total     atomic.Int64
	ctx       context.Context
	cancel    context.CancelCauseFunc
	runner    *TaskRunner
	err       error
	taskLock  sync.Mutex
}

func (p *TaskWarp) SetCompleted(n int64) {
	p.completed.Store(n)
	p.info.Completed = n
}

func (p *TaskWarp) AddCompleted(n int64) {
	p.completed.Add(n)
	p.info.Completed += n
}

func (p *TaskWarp) GetCompleted() int64 {
	return p.completed.Load()
}

func (p *TaskWarp) SetTotal(n int64) {
	p.total.Store(n)
	p.info.Total = n
}

func (p *TaskWarp) AddTotal(n int64) {
	p.total.Add(n)
	p.info.Total += n
}

func (p *TaskWarp) GetTotal() int64 {
	return p.total.Load()
}

func (p *TaskWarp) SetMsg(msg string) error {
	p.info.Msg = msg
	return service.ReplaceTask(p.info)
}

func (p *TaskWarp) UpdateProgress() {
	p.info.Completed = p.GetCompleted()
	p.info.Total = p.GetTotal()
	err := service.ReplaceTask(p.info)
	if err != nil {
		zap.L().Error("failed to update progress for task", zap.Error(err), zap.Int64("id", p.info.Id))
	}
}

func (p *TaskWarp) recoverRun() (err error) {
	defer func() {
		if pc := recover(); pc != nil {
			zap.L().Error("run task occurred panic", zap.Any("panic", pc))
			err = ErrOccurredPanic
		}
	}()
	return p.runner.runCB(p)
}

func (p *TaskWarp) run() {
	p.taskLock.Lock()
	defer p.taskLock.Unlock()
	defer p.UpdateProgress()
	defer p.cancel(ErrTaskFinished)
	err := p.getErr()
	if err == nil {
		p.err = p.recoverRun()
	}
	err = p.getErr()
	p.info.Completed = p.completed.Load()
	if err != nil {
		if errors.Is(err, ErrShutdown) || errors.Is(err, ErrTaskPaused) {
			err = service.UpdateStatus(p.info, model.TaskStatus_Paused, "")
			sendTaskStatus(p.runner.taskType, p.info.Id, model.TaskStatus_Paused, "", p.info.Completed)
		} else if !errors.Is(err, ErrTaskFinished) {
			zap.L().Error("failed to run task", zap.Error(err), zap.Any("task", p.info))
			msg := err.Error()
			err = service.UpdateStatus(p.info, model.TaskStatus_Failed, msg)
			sendTaskStatus(p.runner.taskType, p.info.Id, model.TaskStatus_Failed, msg, p.info.Completed)
		}
	} else {
		err = service.UpdateStatus(p.info, model.TaskStatus_Finished, "")
		sendTaskStatus(p.runner.taskType, p.info.Id, model.TaskStatus_Finished, "", p.info.Completed)
	}
	if err != nil {
		zap.L().Error("failed to update task status in database", zap.Error(err))
	}
}

func (p *TaskWarp) getErr() error {
	err := context.Cause(p.ctx)
	if err == nil {
		err = p.err
	}
	return err
}

func (p *TaskWarp) stop() {
	defer p.taskLock.Unlock()
	if p.taskLock.TryLock() {
		p.cancel(ErrTaskPaused)
	} else {
		p.cancel(ErrTaskPaused)
		p.taskLock.Lock()
	}
}
